﻿#define UseFxCompressor

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using DeflateLib;


namespace TgzSample
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                PrintUsage(); return;
            }

            DateTime startTime = DateTime.Now;
            try
            {
                switch (args[0].ToLowerInvariant())
                {
                    case "-index":
                        IndexTgz(args[1]);
                        break;
                    case "-list":
                        ListTgz(args[1]);
                        break;
                    case "-get":
                        GetFromTgz(args[1], args[2], args.Length < 4 ? null : args[3]);
                        break;
                    case "-list0":
                        ListTgzWithoutHints(args[1]);
                        break;
                    case "-get0":
                        GetFromTgzWithoutHints(args[1], args[2], args.Length < 4 ? null : args[3]);
                        break;
                    default:
                        Console.WriteLine("Unknown options");
                        PrintUsage();
                        return;
                }
                DateTime endTime = DateTime.Now;
                Console.WriteLine("Execution time: {0} sec", endTime - startTime);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error: " + ex);
            }
        }

        private static void InitializeTgzStream(Stream gzStream)
        {
            var header = GZipUtils.ReadGZipInfo(gzStream);
        }

        private static void DeinitializeTgzStream(Stream gzStream)
        {
#if !UseFxCompressor
            uint crc, isize;
            GZipUtils.ReadCrcAndISize(gzStream, out crc, out isize);
#endif
        }

        private static void GetFromTgz(string tgzPath, string filename, string destination)
        {
            EnsureIndex(tgzPath);
            ExpandDestination(filename, ref destination);
            Console.WriteLine("Finding {0} ...", filename);

            string indexPath = GetIndexPath(tgzPath);
            using (FileStream source = File.OpenRead(tgzPath),
                target = File.OpenRead(indexPath))
            {
                InitializeTgzStream(source);
                DeflateHintStream tarStream = new DeflateHintStream(source, target);

                TarHeader header;
                bool isFileFound = false;
                while ((header = TarUtils.ReadHeader(tarStream)) != null)
                {
                    if (String.Equals(header.Fullname, filename))
                    {
                        Console.WriteLine("Saving {0}...", destination);
                        CopyToFile(tarStream, destination, header.Filesize.GetValueOrDefault());
                        isFileFound = true;
                        break;
                    }
                    else
                    {
                        long skip = (header.Filesize.GetValueOrDefault() + 511) & ~511L;
                        tarStream.Seek(skip, SeekOrigin.Current);
                    }
                }
                if (!isFileFound)
                {
                    Console.WriteLine("File was not found");
                }
                DeinitializeTgzStream(source);
            }
        }

        private static void ExpandDestination(string filename, ref string destination)
        {
            if (String.IsNullOrEmpty(destination))
                destination = Path.GetFileName(filename);
            else if (Directory.Exists(destination))
                destination = Path.Combine(destination, Path.GetFileName(filename));
        }

        private static void CopyToFile(Stream s, string path, long length)
        {
            byte[] buffer = new byte[8192];
            int read;
            using (FileStream target = File.Create(path))
            {
                if (length > 0)
                {
                    long left = length;
                    while (left > buffer.Length && (read = s.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        target.Write(buffer, 0, read);
                        left -= read;
                    }
                    if ((read = s.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        target.Write(buffer, 0, read);
                    }
                }
            }
        }


        private static void GetFromTgzWithoutHints(string tgzPath, string filename, string destination)
        {
            ExpandDestination(filename, ref destination);

            Console.WriteLine("Finding {0} (without index)...", filename);
            using (FileStream source = File.OpenRead(tgzPath))
            {
                InitializeTgzStream(source);
                using (Stream tarStream = CreateInflateStream(source))
                {
                    TarHeader header;
                    bool isFileFound = false;
                    while ((header = TarUtils.ReadHeader(tarStream)) != null)
                    {
                        if (String.Equals(header.Fullname, filename))
                        {
                            Console.WriteLine("Saving {0}...", destination);
                            CopyToFile(tarStream, destination, header.Filesize.GetValueOrDefault());
                            isFileFound = true;
                            break;
                        }
                        else
                        {
                            long skip = (header.Filesize.GetValueOrDefault() + 511) & ~511L;
                            SkipByRead(tarStream, skip);
                        }
                    }
                    if (!isFileFound)
                    {
                        Console.WriteLine("File was not found");
                    }
                }
                DeinitializeTgzStream(source);
            }
        }

        private static void ListTgz(string tgzPath)
        {
            EnsureIndex(tgzPath);

            Console.WriteLine("Content of {0} ...", tgzPath);
            string indexPath = GetIndexPath(tgzPath);
            using (FileStream source = File.OpenRead(tgzPath),
                target = File.OpenRead(indexPath))
            {
                InitializeTgzStream(source); 
                
                DeflateHintStream tarStream = new DeflateHintStream(source, target);

                TarHeader header;
                while ((header = TarUtils.ReadHeader(tarStream)) != null)
                {
                    Console.WriteLine(header.Fullname);
                    long skip = (header.Filesize.GetValueOrDefault() + 511) & ~511L;
                    tarStream.Seek(skip, SeekOrigin.Current);
                }
                DeinitializeTgzStream(source);
            }
        }

        private static Stream CreateInflateStream(Stream s)
        {
#if UseFxCompressor
            return new System.IO.Compression.DeflateStream(s, System.IO.Compression.CompressionMode.Decompress, true);
#else
            return new DeflateStream(s, CompressionMode.Decompress, true);
#endif
        }

        private static void ListTgzWithoutHints(string tgzPath)
        {
            Console.WriteLine("Content of {0} (without index) ...", tgzPath);
            using (FileStream source = File.OpenRead(tgzPath))
            {
                InitializeTgzStream(source);
                using (Stream tarStream = CreateInflateStream(source))
                {
                    TarHeader header;
                    while ((header = TarUtils.ReadHeader(tarStream)) != null)
                    {
                        Console.WriteLine(header.Fullname);
                        long skip = (header.Filesize.GetValueOrDefault() + 511) & ~511L;
                        SkipByRead(tarStream, skip);
                    }
                }
                DeinitializeTgzStream(source);
            }
        }

        private static void SkipByRead(Stream s, long skip)
        {
            byte[] buffer = new byte[8192];
            while (skip > buffer.Length)
            {
                s.Read(buffer, 0, buffer.Length);
                skip -= buffer.Length;
            }
            s.Read(buffer, 0, (int)skip);
        }

        private static void IndexTgz(string tgzPath)
        {
            string indexPath = GetIndexPath(tgzPath);
            Console.WriteLine("Build index {0} ...", indexPath);
            using (FileStream source = File.OpenRead(tgzPath),
                target = File.Create(indexPath))
            {
                InitializeTgzStream(source);
                DeflateHintCreator creator = new DeflateHintCreator();
                creator.Process(source, target);
                DeinitializeTgzStream(source);
            }
        }

        private static string GetIndexPath(string tgzPath)
        {
            return Path.ChangeExtension(tgzPath, ".gzindex");
        }

        private static void EnsureIndex(string tgzPath)
        {
            string indexPath = GetIndexPath(tgzPath);
            if (!File.Exists(indexPath))
            {
                Console.WriteLine("Index was not found");
                IndexTgz(tgzPath);
            }
        }


        private static void PrintUsage()
        {
            Console.WriteLine("USAGE: TgzSample.exe -index <tar.gz-file>");
            Console.WriteLine("       TgzSample.exe -list <tar.gz-file>");
            Console.WriteLine("       TgzSample.exe -list0 <tar.gz-file>");
            Console.WriteLine("       TgzSample.exe -get <tar.gz-file> <file-to-extract> [<destination>]");
            Console.WriteLine("       TgzSample.exe -get0 <tar.gz-file> <file-to-extract> [<destination>]");
        }
    }
}
